Machine Learning review and intro to tidymodels
Read through and follow along with the Machine Learning review with an intro to the tidymodels package posted on the Course Materials page.
Tasks:
- Read about the hotel booking data,
hotels, on the Tidy Tuesday page it came from. There is also a link to an article from the original authors. The outcome we will be predicting is called is_canceled.
- Without doing any analysis, what are some variables you think might be predictive and why?
I think that lead_time (days elapsed between entering the date of the booking into the PMS and the arrival date) might be predictive because the reservations that are booked farther out ahead of time may be more likely to be canceled (due to unanticipated things coming up closer to the date of stay that would cause it to be canceled). I think previous_cancellations could be a predictor as well, because that may be a proxy for how reliable that customer is with committing to their booking. I also think that children (number of children) could be a predictor because if there are no children (and if it’s only one adult), then it may be more likely a business trip which isn’t as flexible as, say, a family vacation.
_ What are some problems that might exist with the data? You might think about how it was collected and who did the collecting.
Since customers often change the number of people, length of stay, and preferred room type once they arrive to the hotel, the data may not capture the true distribution of these variables (since the dataset only represents a snapshot in time). It also may be problematic that the data was only collected during summer months, when people are more likely to be on vacation (which could change their probability of canceling versus if it was for work).
- If we construct a model, what type of conclusions will be able to draw from it?
If we construct a model, hopefully we will be able to tell what factors (about the person booking, as well as the hotel) may make the booking more likely to be canceled. I can see how this could be problematic if the model is put into use because it could potentially result in discrimination for who hotels allow to book (if the hotel deems them likely to cancel).
- Create some exploratory plots or table summaries of the variables in the dataset. Be sure to also examine missing values or other interesting values.
#checking to see the distribution of canceled vs. not canceled reservations
ggplot(hotels, aes(x = is_canceled)) +
geom_bar()

#binarizing previous cancellations
hotels <- hotels %>%
mutate(previous_cancellations_bin = ifelse(previous_cancellations == 0, "no", ifelse(previous_cancellations > 0, "yes", NA)))
ggplot(hotels, aes(x = previous_cancellations_bin)) +
geom_bar()
Distributions of the quantitative variables
hotels %>%
select(where(is.numeric)) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value") %>%
ggplot(aes(x = value)) +
geom_histogram() +
facet_wrap(vars(variable),
scales = "free")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## Warning: Removed 4 rows containing non-finite values (stat_bin).

What I noticed:
*One weird spike in arrival_date_day_of_month
*Most are very right-skewed or have the majority of values at 0 or 1 (ex. lead_time, booking_change, babies, adults)
Check the class of each variable:
lapply(hotels, class)
## $hotel
## [1] "character"
##
## $is_canceled
## [1] "numeric"
##
## $lead_time
## [1] "numeric"
##
## $arrival_date_year
## [1] "numeric"
##
## $arrival_date_month
## [1] "character"
##
## $arrival_date_week_number
## [1] "numeric"
##
## $arrival_date_day_of_month
## [1] "numeric"
##
## $stays_in_weekend_nights
## [1] "numeric"
##
## $stays_in_week_nights
## [1] "numeric"
##
## $adults
## [1] "numeric"
##
## $children
## [1] "numeric"
##
## $babies
## [1] "numeric"
##
## $meal
## [1] "character"
##
## $country
## [1] "character"
##
## $market_segment
## [1] "character"
##
## $distribution_channel
## [1] "character"
##
## $is_repeated_guest
## [1] "numeric"
##
## $previous_cancellations
## [1] "numeric"
##
## $previous_bookings_not_canceled
## [1] "numeric"
##
## $reserved_room_type
## [1] "character"
##
## $assigned_room_type
## [1] "character"
##
## $booking_changes
## [1] "numeric"
##
## $deposit_type
## [1] "character"
##
## $agent
## [1] "character"
##
## $company
## [1] "character"
##
## $days_in_waiting_list
## [1] "numeric"
##
## $customer_type
## [1] "character"
##
## $adr
## [1] "numeric"
##
## $required_car_parking_spaces
## [1] "numeric"
##
## $total_of_special_requests
## [1] "numeric"
##
## $reservation_status
## [1] "character"
##
## $reservation_status_date
## [1] "Date"
##
## $previous_cancellations_bin
## [1] "character"
None of the variables are categorical (or binary) at the moment, but ideally hotel, is_cancelled, stays variables, adults, children, meals, etc. should be.
Now, let’s examine the missing values in this dataset.
gg_miss_var(hotels)

#It looks as though children is the only variable with missing values, and there are only four missing values, so I don't think too many NAs is a problem in this dataset
#downsize dataset to a few variables we may care most about
hotels_small <- hotels %>%
select(hotel, is_canceled, lead_time, children, previous_cancellations, adults)
vis_miss(hotels_small)

#looks like there are barely any missing values!
ggplot(hotels_small, aes(x = children, y = as.factor(is_canceled))) +
geom_miss_point()

#So all of the missing values in the children variable are reservations that were cancelled...
- First, we will do a couple things to get the data ready, including making the outcome a factor (needs to be that way for logistic regression), removing the year variable and some reservation status variables, and removing missing values (not NULLs but true missing values). Split the data into a training and test set, stratifying on the outcome variable,
is_canceled. Since we have a lot of data, we’re going to split the data 50/50 between training and test. I have already set.seed() for you. Be sure to use hotels_mod in the splitting.
hotels_mod <- hotels %>%
mutate(is_canceled = as.factor(is_canceled)) %>%
mutate(across(where(is.character), as.factor)) %>%
select(-arrival_date_year,
-reservation_status,
-reservation_status_date) %>%
add_n_miss() %>%
filter(n_miss_all == 0) %>%
select(-n_miss_all)
set.seed(494)
Split data into training and testing (50/50)
#randomly assign 50% of the data to training
hotels_split <- initial_split(hotels_mod, prop = .5)
hotels_split
## <Analysis/Assess/Total>
## <59693/59693/119386>
#training and testing datasets
hotels_training <- training(hotels_split)
hotels_testing <- testing(hotels_split)
- In this next step, we are going to do the pre-processing. Usually, I won’t tell you exactly what to do here, but for your first exercise, I’ll tell you the steps.
- Set up the recipe with
is_canceled as the outcome and all other variables as predictors (HINT: ~.).
- Use a
step_XXX() function or functions (I think there are other ways to do this, but I found step_mutate_at() easiest) to create some indicator variables for the following variables:children,babies, andprevious_cancellations`. So, the new variable should be a 1 if the original is more than 0 and 0 otherwise. Make sure you do this in a way that accounts for values that may be larger than any we see in the dataset.
- For the
agent and company variables, make new indicator variables that are 1 if they have a value of NULL and 0 otherwise.
- Use
fct_lump_n() to lump together countries that aren’t in the top 5 most occurring.
- If you used new names for some of the new variables you created, then remove any variables that are no longer needed.
- Use
step_normalize() to center and scale all the non-categorical predictor variables. (Do this BEFORE creating dummy variables. When I tried to do it after, I ran into an error - I’m still investigating why.)
- Create dummy variables for all factors/categorical predictor variables (make sure you have
-all_outcomes() in this part!!).
- Use the
prep() and juice() functions to apply the steps to the training data just to check that everything went as planned.
hotels_recipe <- recipe(is_canceled ~ ., data = hotels_training) %>%
step_mutate(children = ifelse(children > 0,1,0), babies = ifelse(babies > 0,1,0), previous_cancellations = ifelse(previous_cancellations > 0,1,0)) %>%
step_mutate(agent_null = ifelse(agent == 'NULL', 1, 0), company_null = ifelse(company=='NULL',1,0)) %>%
step_mutate(country = fct_lump_n(country,n=5)) %>%
step_rm(agent, company) %>%
step_normalize(all_predictors(),-all_nominal()) %>% #numeric variables will now have standard deviation of 1 and mean of zero
step_dummy(all_nominal(),-all_outcomes())
#using prep and juice to make sure everything went as planned
hotels_recipe %>%
prep(hotels_training) %>%
juice()
- In this step we will set up a LASSO model and workflow.
- In general, why would we want to use LASSO instead of regular logistic regression? (HINT: think about what happens to the coefficients).
We would want to use LASSO instead of regular logistic regression to better avoid overfitting our model to the training dataset (thereby having it do poorly with the testing and other datasets). LASSO assigns a penalty to less important coefficients to shrink them to zero. The penalty (\(\lambda\) can be from 0 to infinity) that minimizes the sum of the squared residuals is the one that LASSO chooses.
- Define the model type, set the engine, set the
penalty argument to tune() as a placeholder, and set the mode.
hotels_lasso_mod <-
# Define a LASSO model
logistic_reg(mixture=1) %>%
# Set the engine
set_engine("glmnet") %>%
#parameters we will tune
set_args(penalty = tune()) %>%
# Set mode
set_mode("classification")
hotels_lasso_mod
## Logistic Regression Model Specification (classification)
##
## Main Arguments:
## penalty = tune()
## mixture = 1
##
## Computational engine: glmnet
- Create a workflow with the recipe and model. (combines the preprocessing and model definition steps)
hotels_lasso_wf <-
#set up the workflow
workflow() %>%
#add the recipe
add_recipe(hotels_recipe) %>%
#add the modeling
add_model(hotels_lasso_mod)
hotels_lasso_wf
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 6 Recipe Steps
##
## ● step_mutate()
## ● step_mutate()
## ● step_mutate()
## ● step_rm()
## ● step_normalize()
## ● step_dummy()
##
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
##
## Main Arguments:
## penalty = tune()
## mixture = 1
##
## Computational engine: glmnet
- In this step, we’ll tune the model and fit the model using the best tuning parameter to the entire training dataset.
- Create a 5-fold cross-validation sample. We’ll use this later. I have set the seed for you.
- Use the
grid_regular() function to create a grid of 10 potential penalty parameters (we’re keeping this sort of small because the dataset is pretty large). Use that with the 5-fold cv data to tune the model.
- Use the
tune_grid() function to fit the models with different tuning parameters to the different cross-validation sets.
- Use the
collect_metrics() function to collect all the metrics from the previous step and create a plot with the accuracy on the y-axis and the penalty term on the x-axis. Put the x-axis on the log scale.
- Use the
select_best() function to find the best tuning parameter, fit the model using that tuning parameter to the entire training set (HINT: finalize_workflow() and fit()), and display the model results using pull_workflow_fit() and tidy(). Are there some variables with coefficients of 0?
set.seed(494) # for reproducibility
#5-fold cross-validation
hotels_cv <- vfold_cv(hotels_training, v = 5)
#choose penalty parameter values
penalty_grid <- grid_regular(penalty(), levels = 10)
penalty_grid
#fit the models with different tuning parameters to the different cross-validation sets
hotels_lasso_tune <-
hotels_lasso_wf %>%
tune_grid(
resamples = hotels_cv,
grid = penalty_grid
)
hotels_lasso_tune
hotels_accuracy <- hotels_lasso_tune %>%
collect_metrics() %>%
filter(.metric == "accuracy")
hotels_accuracy
ggplot(hotels_accuracy, aes(x = penalty, y = mean)) +
geom_point() +
geom_line() +
scale_x_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10",scales::math_format(10^.x))) +
ggtitle("Accuracy over different penalties") +
ylab("accuracy")

#select best tuning parameter (accuracy-wise)
best_param_hotels <- hotels_lasso_tune %>%
select_best(metric = "accuracy")
best_param_hotels
Fitting the model with the best tuning parameter according to select_best
#create workflow
hotels_lasso_final_wf <- hotels_lasso_wf %>%
finalize_workflow(best_param_hotels)
hotels_lasso_final_wf
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 6 Recipe Steps
##
## ● step_mutate()
## ● step_mutate()
## ● step_mutate()
## ● step_rm()
## ● step_normalize()
## ● step_dummy()
##
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
##
## Main Arguments:
## penalty = 1e-10
## mixture = 1
##
## Computational engine: glmnet
#fit to the training data
hotels_lasso_final_mod <- hotels_lasso_final_wf %>%
fit(data = hotels_training)
hotels_lasso_final_mod %>%
pull_workflow_fit() %>%
tidy()
Yes, some variables now have coefficients of zero: arrival_date_month_October, meal_FB, market_segment_Groups, market_segment_Undefined, distribution_channel_Undefined, assigned_room_type_L, and assigned_room_type_P.
- Now that we have a model, let’s evaluate it a bit more. All we have looked at so far is the cross-validated accuracy from the previous step.
- Create a variable importance graph. Which variables show up as the most important? Are you surprised?
# Visualize variable importance
hotels_lasso_final_mod %>%
pull_workflow_fit() %>%
vip()

From the variable importance plot, we see that reserved_room_type_P, deposit_type_Non.Refund, and assigned_room_type_I are the top three most important predictors of canceling a reservation. I am not sure what room types P and I are, but I am not surprised that putting a non-refundable deposit down is a strong indicator for whether or not the booking got canceled (because people don’t like to lose money so they may be less likely to cancel). If room type indicated the size and number of beds, I could see it being a relevant predictor because people going on business trips (small bed, single room most likely) may be less likely to cancel.
- Use the
last_fit() function to fit the final model and then apply it to the testing data. Report the metrics from the testing data using the collect_metrics() function. How do they compare to the cross-validated metrics?
# Fit model with best tuning parameter(s) to training data and apply to test data
hotels_lasso_test <- hotels_lasso_final_wf %>%
last_fit(hotels_split)
# Metrics for model applied to test data
hotels_lasso_test %>%
collect_metrics()
#metrics for model applied to cv data
hotels_lasso_tune %>%
collect_metrics()
#comparing metrics
ggplot(hotels_accuracy, aes(x = penalty, y = mean)) +
geom_point() +
geom_line() +
scale_x_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10",scales::math_format(10^.x))) +
ggtitle("Accuracy over different penalties") +
ylab("accuracy") +
geom_point(aes(x=0.0004641589, y = 0.8128256, col = "red")) +
labs(color = "Test data metric")

The accuracy of model applied to CV data with the best tuning parameter is SLIGHTLY lower than the accuracy of the model applied to the test data with the same tuning parameter. We can see this in the plot, where the red dot (test data) is slightly higher than the black dot (training data).
- Use the
collect_predictions() function to find the predicted probabilities and classes for the test data. Save this to a new dataset called preds. Then, use the conf_mat() function from dials (part of tidymodels) to create a confusion matrix showing the predicted classes vs. the true classes. What is the true positive rate (sensitivity)? What is the true negative rate (specificity)? See this Wikipedia reference if you (like me) tend to forget these definitions.
# predictions from testing data
preds <- collect_predictions(hotels_lasso_test)
preds
#make a confusion matrix
conf_mat(preds, .pred_class, is_canceled)
## Truth
## Prediction 0 1
## 0 34225 3340
## 1 7782 14346
- Use the
preds dataset you just created to create a density plot of the predicted probabilities of canceling (the variable is called .pred_1), filling by is_canceled. Use an alpha = .5 and color = NA in the geom_density().
ggplot(preds, aes(x = .pred_1, fill = is_canceled)) +
geom_density(alpha = 0.5, color = NA) +
ggtitle("Predicted probabilities of cancelling (test data)")

Answer these questions:
- What would this graph look like for a model with an accuracy that was close to 1?
A model with an accuracy that was close to 1 would have densities almost solely at x-axis values of 0 and 1.
- Our predictions are classified as canceled if their predicted probability of canceling is greater than .5. If we wanted to have a high true positive rate, should we make the cutoff for predicted as canceled higher or lower than .5?
If we wanted a high true positive rate (correctly predicting canceled reservations as canceled) then we would want to make the cutoff for “predicted as canceled” lower than 0.5 because that would assign more predicted probabilities to canceled=yes.
- What happens to the true negative rate if we try to get a higher true positive rate?
If we try to get a true positive rate, the true negative rate will subsequently be lower because we are classifying more reservations as “canceled” which thereby classifies less as “not canceled”.
- Let’s say that this model is going to be applied to bookings 14 days in advance of their arrival at each hotel, and someone who works for the hotel will make a phone call to the person who made the booking. During this phone call, they will try to assure that the person will be keeping their reservation or that they will be canceling in which case they can do that now and still have time to fill the room. How should the hotel go about deciding who to call? How could they measure whether it was worth the effort to do the calling? Can you think of another way they might use the model?
Hotels could use this model to call anyone whom the model predicted would cancel their reservation, to confirm that they still wanted the reservation. They could measure whether pr not it was worth the effort to call by keeping track of the “true negatives” (people they called who actually were planning to cancel their reservation) and “false negatives” (people they called that the model predicted would cancel but weren’t actually planning to cancel) and seeing if the model guessed right more often than it guessed incorrectly (to a significant degree). Another way that this model could be used is to see who it predicts is NOT likely to cancel, and then sending them promotional material or catering to those people in some way to attempt to make them want to continue booking with that hotel company again.
- How might you go about questioning and evaluating the model in terms of fairness? Are there any questions you would like to ask of the people who collected the data?
Depending on how the model would be used in the future, I might challenge their pick of the putting a non-refundable deposit down variable to predict cancellations, because some people are financially or logistically unable to do so. Therefore, if the model caused hotels to give them poorer treatment, that is unjust. I would also like to ask the people who collected the data why they chose to use data from hotels in Portugal, and how much they think the results would change if you compared across different countries.
LS0tCnRpdGxlOiAnQXNzaWdubWVudCAjMScKYXV0aG9yOiAnQ29sbGVlbiBNaW5uaWhhbicKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQoja25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFKQpgYGAKCmBgYHtyIGxpYnJhcmllc30KbGlicmFyeSh0aWR5dmVyc2UpICAgICAgICAgIyBmb3IgZ3JhcGhpbmcgYW5kIGRhdGEgY2xlYW5pbmcKbGlicmFyeSh0aWR5bW9kZWxzKSAgICAgICAgIyBmb3IgbW9kZWxpbmcKbGlicmFyeShuYW5pYXIpICAgICAgICAgICAgIyBmb3IgYW5hbHl6aW5nIG1pc3NpbmcgdmFsdWVzCmxpYnJhcnkodmlwKSAgICAgICAgICAgICAgICMgZm9yIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMKdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkgIyBMaXNhJ3MgZmF2b3JpdGUgdGhlbWUKYGBgCgpgYGB7ciBkYXRhfQpob3RlbHMgPC0gcmVhZHI6OnJlYWRfY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L21hc3Rlci9kYXRhLzIwMjAvMjAyMC0wMi0xMS9ob3RlbHMuY3N2JykKYGBgCgoKV2hlbiB5b3UgZmluaXNoIHRoZSBhc3NpZ25tZW50LCByZW1vdmUgdGhlIGAjYCBmcm9tIHRoZSBvcHRpb25zIGNodW5rIGF0IHRoZSB0b3AsIHNvIHRoYXQgbWVzc2FnZXMgYW5kIHdhcm5pbmdzIGFyZW4ndCBwcmludGVkLiBJZiB5b3UgYXJlIGdldHRpbmcgZXJyb3JzIGluIHlvdXIgY29kZSwgYWRkIGBlcnJvciA9IFRSVUVgIHNvIHRoYXQgdGhlIGZpbGUga25pdHMuIEkgd291bGQgcmVjb21tZW5kIG5vdCByZW1vdmluZyB0aGUgYCNgIHVudGlsIHlvdSBhcmUgY29tcGxldGVseSBmaW5pc2hlZC4KCiMjIFNldHRpbmcgdXAgR2l0IGFuZCBHaXRIdWIgaW4gUlN0dWRpbwoKUmVhZCB0aGUgW1F1aWNrIEludHJvXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMS0yOC1naXRnaXRodWIvI3F1aWNrLWludHJvKSBzZWN0aW9uIG9mIHRoZSBVc2luZyBnaXQgYW5kIEdpdEh1YiBpbiBSIFN0dWRpbyBzZXQgb2YgQ291cnNlIE1hdGVyaWFscy4gU2V0IHVwIEdpdCBhbmQgR2l0SHViIGFuZCBjcmVhdGUgYSBHaXRIdWIgcmVwbyBhbmQgYXNzb2NpYXRlZCBSIFByb2plY3QgKGRvbmUgZm9yIHlvdSB3aGVuIHlvdSBjbG9uZSB0aGUgcmVwbykgZm9yIHRoaXMgaG9tZXdvcmsgYXNzaWdubWVudC4gUHV0IHRoaXMgZmlsZSBpbnRvIHRoZSBwcm9qZWN0LiBZb3Ugc2hvdWxkIGFsd2F5cyBvcGVuIHRoZSBSIFByb2plY3QgKC5ScHJvaikgZmlsZSB3aGVuIHlvdSB3b3JrIHdpdGggYW55IG9mIHRoZSBmaWxlcyBpbiB0aGUgcHJvamVjdC4gCgoqKlRhc2sqKjogQmVsb3csIHBvc3QgYSBsaW5rIHRvIHlvdXIgR2l0SHViIHJlcG9zaXRvcnkuCgpodHRwczovL2dpdGh1Yi5jb20vbmVlbGxvYzI4L0FkdmFuY2VkLWRhdGFzY2ktaHcxLmdpdAoKCiMjIENyZWF0aW5nIGEgd2Vic2l0ZQoKWW91J2xsIGJlIHVzaW5nIFJTdHVkaW8gdG8gY3JlYXRlIGEgcGVyc29uYWwgd2Vic2l0ZSB0byBzaG93Y2FzZSB5b3VyIHdvcmsgZnJvbSB0aGlzIGNsYXNzISBTdGFydCBieSB3YXRjaGluZyB0aGUgW1NoYXJpbmcgb24gU2hvcnQgTm90aWNlXShodHRwczovL3JzdHVkaW8uY29tL3Jlc291cmNlcy93ZWJpbmFycy9zaGFyaW5nLW9uLXNob3J0LW5vdGljZS1ob3ctdG8tZ2V0LXlvdXItbWF0ZXJpYWxzLW9ubGluZS13aXRoLXItbWFya2Rvd24vKSB3ZWJpbmFyIGJ5IEFsaXNvbiBIaWxsIGFuZCBEZXNpcsOpZSBEZSBMZW9uIG9mIFJTdHVkaW8uIFRoaXMgc2hvdWxkIGhlbHAgeW91IGNob29zZSB0aGUgdHlwZSBvZiB3ZWJzaXRlIHlvdSdkIGxpa2UgdG8gY3JlYXRlLiAKCk9uY2UgeW91J3ZlIGNob3NlbiB0aGF0LCB5b3UgbWlnaHQgd2FudCB0byBsb29rIHRocm91Z2ggc29tZSBvZiB0aGUgb3RoZXIgKkJ1aWxkaW5nIGEgd2Vic2l0ZSogcmVzb3VyY2VzIEkgcG9zdGVkIG9uIHRoZSBbcmVzb3VyY2VzIHBhZ2VdKGh0dHBzOi8vYWR2YW5jZWQtZHMtaW4tci5uZXRsaWZ5LmFwcC9yZXNvdXJjZXMuaHRtbCkgb2Ygb3VyIGNvdXJzZSB3ZWJzaXRlLiBJIGhpZ2hseSByZWNvbW1lbmQgbWFraW5nIGEgbmljZSBsYW5kaW5nIHBhZ2Ugd2hlcmUgeW91IGdpdmUgYSBicmllZiBpbnRyb2R1Y3Rpb24gb2YgeW91cnNlbGYuIAoKCioqVGFza3MqKjoKCiogSW5jbHVkZSBhIGxpbmsgdG8geW91ciB3ZWJzaXRlIGJlbG93LiAoSWYgYW55b25lIGRvZXMgbm90IHdhbnQgdG8gcG9zdCBhIHdlYnNpdGUgcHVibGljbHksIHBsZWFzZSB0YWxrIHRvIG1lIGFuZCB3ZSB3aWxsIGZpbmQgYSBkaWZmZXJlbnQgc29sdXRpb24pLgoKW015IHdlYnNpdGVdKGh0dHBzOi8vY29sbGVlbm1pbm5paGFuLm5ldGxpZnkuYXBwKQoKKiBMaXN0ZW4gdG8gYXQgbGVhc3QgdGhlIGZpcnN0IDIwIG1pbnV0ZXMgb2YgIkJ1aWxkaW5nIGEgQ2FyZWVyIGluIERhdGEgU2NpZW5jZSwgQ2hhcHRlciA0OiBCdWlsZGluZyBhIFBvcnRmb2xpbyIuIEdvIHRvIHRoZSBtYWluIFtwb2RjYXN0IHdlYnNpdGVdKGh0dHBzOi8vcG9kY2FzdC5iZXN0Ym9vay5jb29sLykgYW5kIG5hdmlnYXRlIHRvIGEgcG9kY2FzdCBwcm92aWRlciB0aGF0IHdvcmtzIGZvciB5b3UgdG8gZmluZCB0aGF0IHNwZWNpZmljIGVwaXNvZGUuIFdyaXRlIDItMyBzZW50ZW5jZXMgcmVmbGVjdGluZyBvbiB3aGF0IHRoZXkgZGlzY3Vzc2VkIGFuZCB3aHkgY3JlYXRpbmcgYSB3ZWJzaXRlIG1pZ2h0IGJlIGhlbHBmdWwgZm9yIHlvdS4KCioqSmFjcXVlbGluZSBhbmQgRW1pbHkgbWVudGlvbmVkIGhvdyBjcmVhdGluZyBhIHBvcnRmb2xpbyAod2Vic2l0ZSkgY2FuIHNlcnZlIG11bHRpcGxlIHB1cnBvc2VzLCBmcm9tIG1vdGl2YXRpbmcgeW91IHRvIGdhaW4gbmV3IHNraWxscywgdG8gZ2V0dGluZyB5b3UgaW52b2x2ZWQgaW4gdGhlIGRhdGEgc2NpZW5jZSBjb21tdW5pdHksIHRvIGhlbHBpbmcgeW91IGhhdmUgc29tZXRoaW5nIHRvIHNob3cgYXQgYSBqb2IgaW50ZXJ2aWV3LiAgSSBmZWx0IGluc3BpcmVkIHRvIHBvdGVudGlhbGx5IHB1cnN1ZSBwZXJzb25hbCBwcm9qZWN0cyBpbiB0aGUgZnV0dXJlIHdoZW4gdGhleSBzdWdnZXN0ZWQgdGhhdCB5b3UgdGhpbmsgb2YgYSBxdWVzdGlvbiB0aGF0IGdlbnVpbmVseSBpbnRlcmVzdHMgeW91IGFuZCB0YWtlIGl0IGZyb20gdGhlcmUgKGV2ZW4gaWYgaXQgZG9lc24ndCBzcGVjaWZpY2FsbHkgaGF2ZSB0byBkbyB3aXRoIHRoZSBpbmR1c3RyeSB5b3UgYXJlIGluL2hvcGUgdG8gYmUgaW4pLCBiZWNhdXNlIHlvdSBjYW4gYXBwbHkgdGhvc2Ugc2tpbGxzIHlvdSBsZWFybiBpbiBvdGhlciBjb250ZXh0cy4gSSB0aGluayB0aGF0IGNyZWF0aW5nIGEgd2Vic2l0ZSB3aWxsIGJlIGhlbHBmdWwgZm9yIG1lIHNvIHRoYXQgSSBjYW4gaGF2ZSBhIGNvbnNvbGlkYXRlZCBzcGFjZSB0aGF0IGhvbGRzIG15IHByb2plY3RzLCB3aGljaCB3aWxsIGhvcGVmdWxseSBpbnNwaXJlIG1lIHRvIGNvbnRpbnVvdXNseSBhZGQgdG8gaXQgb3ZlciB0aW1lLioqCgoqIChPcHRpb25hbCkgQ3JlYXRlIGFuIFIgcGFja2FnZSB3aXRoIHlvdXIgb3duIGN1c3RvbWl6ZWQgYGdwcGxvdDJgIHRoZW1lISBXcml0ZSBhIHBvc3Qgb24geW91ciB3ZWJzaXRlIGFib3V0IHdoeSB5b3UgbWFkZSB0aGUgY2hvaWNlcyB5b3UgZGlkIGZvciB0aGUgdGhlbWUuIFNlZSB0aGUgKkJ1aWxkaW5nIGFuIFIgcGFja2FnZSogYW5kICpDdXN0b20gYGdncGxvdDJgIHRoZW1lcyogW3Jlc291cmNlc10oaHR0cHM6Ly9hZHZhbmNlZC1kcy1pbi1yLm5ldGxpZnkuYXBwL3Jlc291cmNlcy5odG1sKS4gCgoqKkkgcmFuIGludG8gc29tZSBlcnJvcnMgdGhhdCBJIGNvdWxkbid0IGZpZ3VyZSBvdXQgd2hlbiB0cnlpbmcgdG8gbWFrZSBteSBwYWNrYWdlLCBzbyBJIHdpbGwganVzdCBsZWF2ZSB0aGUgdGhlbWUgaGVyZSBmb3Igbm93IGFuZCB0cnkgdG8gdHJvdWJsZXNob290IGFkZGluZyBpdCB0byBhbiBSIHBhY2thZ2UgbGF0ZXIuKioKYGBge3J9CnRoZW1lX2JlYWNoeSA8LSBmdW5jdGlvbiAoKSB7CiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxMSwgYmFzZV9mYW1pbHkgPSAiQXZlbmlyIikgJStyZXBsYWNlJQogICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgI3JlbW92ZXMgd2hpdGUgYmFja2dyb3VuZAogICAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAibGlnaHRjeWFuMSIsIGNvbG9yID0gTkEpLCAjY2hhbmdlIGJhY2tncm91bmQgdG8gYSBwYWxlIGJsdWUKICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gMC4zKSwgI3RoaW5uaW5nIG91dCBheGlzIGxpbmVzCiAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwwLjk1KSwgI3B1dHMgbGVnZW5kIGluc2lkZSBvZiBwbG90CiAgICAgICAgICBsZWdlbmQuYm94LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0idHJhbnNwYXJlbnQiLCBjb2xvdXI9IE5BKSwgI21ha2UgbGVnZW5kIGJveCBiYWNrZ3JvdW5kCiAgICAgICAgICBsZWdlbmQuYm94Lm1hcmdpbiA9IG1hcmdpbig2LDYsNiw2KSwgI21hcmdpbiBhcm91bmQgbGVnZW5kCiAgICAgICAgICBsZWdlbmQuanVzdGlmaWNhdGlvbiA9IGMoInJpZ2h0IiwidG9wIiksCiAgICAgICAgICBsZWdlbmQuYm94Lmp1c3QgPSAicmlnaHQiLAogICAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QobGluZXR5cGUgPSAxLCBzaXplID0gMC4zLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSAibGlnaHR5ZWxsb3ciKQogICAgICAgICAgCiAgICAgICAgICAgICkKfQoKI2V4YW1wbGUgb2YgdGhlIHRoZW1lIHVzZWQgb24gdGhlIGhvdGVscyBkYXRhc2V0CgpnZ3Bsb3QoaG90ZWxzLCBhZXMoeCA9IG1lYWwsIGZpbGwgPSBhcy5mYWN0b3IoaXNfY2FuY2VsZWQpKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gInN0YWNrIikgKwogIGdndGl0bGUoIkNhbmNlbGxhdGlvbnMgYnkgbWVhbCB0eXBlIikrCiAgdGhlbWVfYmVhY2h5KCkgKwogIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJDYW5jZWxsYXRpb24gU3RhdHVzIiwgYnJlYWtzID0gYygwLDEpLCBsYWJlbHMgPSBjKCJEaWRuJ3QgQ2FuY2VsIiwgIkNhbmNlbGVkIikpCmBgYAoKCiMjIE1hY2hpbmUgTGVhcm5pbmcgcmV2aWV3IGFuZCBpbnRybyB0byBgdGlkeW1vZGVsc2AKClJlYWQgdGhyb3VnaCBhbmQgZm9sbG93IGFsb25nIHdpdGggdGhlIFtNYWNoaW5lIExlYXJuaW5nIHJldmlldyB3aXRoIGFuIGludHJvIHRvIHRoZSBgdGlkeW1vZGVsc2AgcGFja2FnZV0oaHR0cHM6Ly9hZHZhbmNlZC1kcy1pbi1yLm5ldGxpZnkuYXBwL3Bvc3RzLzIwMjEtMDMtMTYtbWwtcmV2aWV3LykgcG9zdGVkIG9uIHRoZSBDb3Vyc2UgTWF0ZXJpYWxzIHBhZ2UuIAoKKipUYXNrcyoqOgoKMS4gUmVhZCBhYm91dCB0aGUgaG90ZWwgYm9va2luZyBkYXRhLCBgaG90ZWxzYCwgb24gdGhlIFtUaWR5IFR1ZXNkYXkgcGFnZV0oaHR0cHM6Ly9naXRodWIuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9ibG9iL21hc3Rlci9kYXRhLzIwMjAvMjAyMC0wMi0xMS9yZWFkbWUubWQpIGl0IGNhbWUgZnJvbS4gVGhlcmUgaXMgYWxzbyBhIGxpbmsgdG8gYW4gYXJ0aWNsZSBmcm9tIHRoZSBvcmlnaW5hbCBhdXRob3JzLiBUaGUgb3V0Y29tZSB3ZSB3aWxsIGJlIHByZWRpY3RpbmcgaXMgY2FsbGVkIGBpc19jYW5jZWxlZGAuIAoKICAtIFdpdGhvdXQgZG9pbmcgYW55IGFuYWx5c2lzLCB3aGF0IGFyZSBzb21lIHZhcmlhYmxlcyB5b3UgdGhpbmsgbWlnaHQgYmUgcHJlZGljdGl2ZSBhbmQgd2h5PyAgCiAgCiAgKipJIHRoaW5rIHRoYXQgbGVhZF90aW1lIChkYXlzIGVsYXBzZWQgYmV0d2VlbiBlbnRlcmluZyB0aGUgZGF0ZSBvZiB0aGUgYm9va2luZyBpbnRvIHRoZSBQTVMgYW5kIHRoZSBhcnJpdmFsIGRhdGUpIG1pZ2h0IGJlIHByZWRpY3RpdmUgYmVjYXVzZSB0aGUgcmVzZXJ2YXRpb25zIHRoYXQgYXJlIGJvb2tlZCBmYXJ0aGVyIG91dCBhaGVhZCBvZiB0aW1lIG1heSBiZSBtb3JlIGxpa2VseSB0byBiZSBjYW5jZWxlZCAoZHVlIHRvIHVuYW50aWNpcGF0ZWQgdGhpbmdzIGNvbWluZyB1cCBjbG9zZXIgdG8gdGhlIGRhdGUgb2Ygc3RheSB0aGF0IHdvdWxkIGNhdXNlIGl0IHRvIGJlIGNhbmNlbGVkKS4gIEkgdGhpbmsgcHJldmlvdXNfY2FuY2VsbGF0aW9ucyBjb3VsZCBiZSBhIHByZWRpY3RvciBhcyB3ZWxsLCBiZWNhdXNlIHRoYXQgbWF5IGJlIGEgcHJveHkgZm9yIGhvdyByZWxpYWJsZSB0aGF0IGN1c3RvbWVyIGlzIHdpdGggY29tbWl0dGluZyB0byB0aGVpciBib29raW5nLiAgSSBhbHNvIHRoaW5rIHRoYXQgY2hpbGRyZW4gKG51bWJlciBvZiBjaGlsZHJlbikgY291bGQgYmUgYSBwcmVkaWN0b3IgYmVjYXVzZSBpZiB0aGVyZSBhcmUgbm8gY2hpbGRyZW4gKGFuZCBpZiBpdCdzIG9ubHkgb25lIGFkdWx0KSwgdGhlbiBpdCBtYXkgYmUgbW9yZSBsaWtlbHkgYSBidXNpbmVzcyB0cmlwIHdoaWNoIGlzbid0IGFzIGZsZXhpYmxlIGFzLCBzYXksIGEgZmFtaWx5IHZhY2F0aW9uLioqCiAgCiAgXyBXaGF0IGFyZSBzb21lIHByb2JsZW1zIHRoYXQgbWlnaHQgZXhpc3Qgd2l0aCB0aGUgZGF0YT8gWW91IG1pZ2h0IHRoaW5rIGFib3V0IGhvdyBpdCB3YXMgY29sbGVjdGVkIGFuZCB3aG8gZGlkIHRoZSBjb2xsZWN0aW5nLiAgCiAgCiAgKipTaW5jZSBjdXN0b21lcnMgb2Z0ZW4gY2hhbmdlIHRoZSBudW1iZXIgb2YgcGVvcGxlLCBsZW5ndGggb2Ygc3RheSwgYW5kIHByZWZlcnJlZCByb29tIHR5cGUgb25jZSB0aGV5IGFycml2ZSB0byB0aGUgaG90ZWwsIHRoZSBkYXRhIG1heSBub3QgY2FwdHVyZSB0aGUgdHJ1ZSBkaXN0cmlidXRpb24gb2YgdGhlc2UgdmFyaWFibGVzIChzaW5jZSB0aGUgZGF0YXNldCBvbmx5IHJlcHJlc2VudHMgYSBzbmFwc2hvdCBpbiB0aW1lKS4gIEl0IGFsc28gbWF5IGJlIHByb2JsZW1hdGljIHRoYXQgdGhlIGRhdGEgd2FzIG9ubHkgY29sbGVjdGVkIGR1cmluZyBzdW1tZXIgbW9udGhzLCB3aGVuIHBlb3BsZSBhcmUgbW9yZSBsaWtlbHkgdG8gYmUgb24gdmFjYXRpb24gKHdoaWNoIGNvdWxkIGNoYW5nZSB0aGVpciBwcm9iYWJpbGl0eSBvZiBjYW5jZWxpbmcgdmVyc3VzIGlmIGl0IHdhcyBmb3Igd29yaykuKioKICAKICAtIElmIHdlIGNvbnN0cnVjdCBhIG1vZGVsLCB3aGF0IHR5cGUgb2YgY29uY2x1c2lvbnMgd2lsbCBiZSBhYmxlIHRvIGRyYXcgZnJvbSBpdD8gIAogIAogICoqSWYgd2UgY29uc3RydWN0IGEgbW9kZWwsIGhvcGVmdWxseSB3ZSB3aWxsIGJlIGFibGUgdG8gdGVsbCB3aGF0IGZhY3RvcnMgKGFib3V0IHRoZSBwZXJzb24gYm9va2luZywgYXMgd2VsbCBhcyB0aGUgaG90ZWwpIG1heSBtYWtlIHRoZSBib29raW5nIG1vcmUgbGlrZWx5IHRvIGJlIGNhbmNlbGVkLiAgSSBjYW4gc2VlIGhvdyB0aGlzIGNvdWxkIGJlIHByb2JsZW1hdGljIGlmIHRoZSBtb2RlbCBpcyBwdXQgaW50byB1c2UgYmVjYXVzZSBpdCBjb3VsZCBwb3RlbnRpYWxseSByZXN1bHQgaW4gZGlzY3JpbWluYXRpb24gZm9yIHdobyBob3RlbHMgYWxsb3cgdG8gYm9vayAoaWYgdGhlIGhvdGVsIGRlZW1zIHRoZW0gbGlrZWx5IHRvIGNhbmNlbCkuKioKICAKMi4gQ3JlYXRlIHNvbWUgZXhwbG9yYXRvcnkgcGxvdHMgb3IgdGFibGUgc3VtbWFyaWVzIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQuIEJlIHN1cmUgdG8gYWxzbyBleGFtaW5lIG1pc3NpbmcgdmFsdWVzIG9yIG90aGVyIGludGVyZXN0aW5nIHZhbHVlcy4KYGBge3J9CiNjaGVja2luZyB0byBzZWUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjYW5jZWxlZCB2cy4gbm90IGNhbmNlbGVkIHJlc2VydmF0aW9ucwpnZ3Bsb3QoaG90ZWxzLCBhZXMoeCA9IGlzX2NhbmNlbGVkKSkgKwogIGdlb21fYmFyKCkKCiNiaW5hcml6aW5nIHByZXZpb3VzIGNhbmNlbGxhdGlvbnMKaG90ZWxzIDwtIGhvdGVscyAlPiUKICBtdXRhdGUocHJldmlvdXNfY2FuY2VsbGF0aW9uc19iaW4gPSBpZmVsc2UocHJldmlvdXNfY2FuY2VsbGF0aW9ucyA9PSAwLCAibm8iLCBpZmVsc2UocHJldmlvdXNfY2FuY2VsbGF0aW9ucyA+IDAsICJ5ZXMiLCBOQSkpKQoKZ2dwbG90KGhvdGVscywgYWVzKHggPSBwcmV2aW91c19jYW5jZWxsYXRpb25zX2JpbikpICsKICBnZW9tX2JhcigpCmBgYApEaXN0cmlidXRpb25zIG9mIHRoZSBxdWFudGl0YXRpdmUgdmFyaWFibGVzCmBgYHtyfQpob3RlbHMgJT4lIAogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsIAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oKSArCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwgCiAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKV2hhdCBJIG5vdGljZWQ6CgoqT25lIHdlaXJkIHNwaWtlIGluIGFycml2YWxfZGF0ZV9kYXlfb2ZfbW9udGgKCipNb3N0IGFyZSB2ZXJ5IHJpZ2h0LXNrZXdlZCBvciBoYXZlIHRoZSBtYWpvcml0eSBvZiB2YWx1ZXMgYXQgMCBvciAxIChleC4gbGVhZF90aW1lLCBib29raW5nX2NoYW5nZSwgYmFiaWVzLCBhZHVsdHMpCgpDaGVjayB0aGUgY2xhc3Mgb2YgZWFjaCB2YXJpYWJsZToKYGBge3J9CmxhcHBseShob3RlbHMsIGNsYXNzKQpgYGAKCk5vbmUgb2YgdGhlIHZhcmlhYmxlcyBhcmUgY2F0ZWdvcmljYWwgKG9yIGJpbmFyeSkgYXQgdGhlIG1vbWVudCwgYnV0IGlkZWFsbHkgaG90ZWwsIGlzX2NhbmNlbGxlZCwgc3RheXMgdmFyaWFibGVzLCBhZHVsdHMsIGNoaWxkcmVuLCBtZWFscywgZXRjLiBzaG91bGQgYmUuCgoKTm93LCBsZXQncyBleGFtaW5lIHRoZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGlzIGRhdGFzZXQuCmBgYHtyfQpnZ19taXNzX3Zhcihob3RlbHMpCiNJdCBsb29rcyBhcyB0aG91Z2ggY2hpbGRyZW4gaXMgdGhlIG9ubHkgdmFyaWFibGUgd2l0aCBtaXNzaW5nIHZhbHVlcywgYW5kIHRoZXJlIGFyZSBvbmx5IGZvdXIgbWlzc2luZyB2YWx1ZXMsIHNvIEkgZG9uJ3QgdGhpbmsgdG9vIG1hbnkgTkFzIGlzIGEgcHJvYmxlbSBpbiB0aGlzIGRhdGFzZXQKCiNkb3duc2l6ZSBkYXRhc2V0IHRvIGEgZmV3IHZhcmlhYmxlcyB3ZSBtYXkgY2FyZSBtb3N0IGFib3V0CmhvdGVsc19zbWFsbCA8LSBob3RlbHMgJT4lCiAgc2VsZWN0KGhvdGVsLCBpc19jYW5jZWxlZCwgbGVhZF90aW1lLCBjaGlsZHJlbiwgcHJldmlvdXNfY2FuY2VsbGF0aW9ucywgYWR1bHRzKQogIAp2aXNfbWlzcyhob3RlbHNfc21hbGwpCiNsb29rcyBsaWtlIHRoZXJlIGFyZSBiYXJlbHkgYW55IG1pc3NpbmcgdmFsdWVzIQoKZ2dwbG90KGhvdGVsc19zbWFsbCwgYWVzKHggPSBjaGlsZHJlbiwgeSA9IGFzLmZhY3Rvcihpc19jYW5jZWxlZCkpKSArCiAgZ2VvbV9taXNzX3BvaW50KCkKI1NvIGFsbCBvZiB0aGUgbWlzc2luZyB2YWx1ZXMgaW4gdGhlIGNoaWxkcmVuIHZhcmlhYmxlIGFyZSByZXNlcnZhdGlvbnMgdGhhdCB3ZXJlIGNhbmNlbGxlZC4uLgpgYGAKCgoKMy4gRmlyc3QsIHdlIHdpbGwgZG8gYSBjb3VwbGUgdGhpbmdzIHRvIGdldCB0aGUgZGF0YSByZWFkeSwgaW5jbHVkaW5nIG1ha2luZyB0aGUgb3V0Y29tZSBhIGZhY3RvciAobmVlZHMgdG8gYmUgdGhhdCB3YXkgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24pLCByZW1vdmluZyB0aGUgeWVhciB2YXJpYWJsZSBhbmQgc29tZSByZXNlcnZhdGlvbiBzdGF0dXMgdmFyaWFibGVzLCBhbmQgcmVtb3ZpbmcgbWlzc2luZyB2YWx1ZXMgKG5vdCBOVUxMcyBidXQgdHJ1ZSBtaXNzaW5nIHZhbHVlcykuIFNwbGl0IHRoZSBkYXRhIGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQsIHN0cmF0aWZ5aW5nIG9uIHRoZSBvdXRjb21lIHZhcmlhYmxlLCBgaXNfY2FuY2VsZWRgLiBTaW5jZSB3ZSBoYXZlIGEgbG90IG9mIGRhdGEsIHdlJ3JlIGdvaW5nIHRvIHNwbGl0IHRoZSBkYXRhIDUwLzUwIGJldHdlZW4gdHJhaW5pbmcgYW5kIHRlc3QuIEkgaGF2ZSBhbHJlYWR5IGBzZXQuc2VlZCgpYCBmb3IgeW91LiBCZSBzdXJlIHRvIHVzZSBgaG90ZWxzX21vZGAgaW4gdGhlIHNwbGl0dGluZy4KCmBgYHtyfQpob3RlbHNfbW9kIDwtIGhvdGVscyAlPiUgCiAgbXV0YXRlKGlzX2NhbmNlbGVkID0gYXMuZmFjdG9yKGlzX2NhbmNlbGVkKSkgJT4lIAogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwgYXMuZmFjdG9yKSkgJT4lIAogIHNlbGVjdCgtYXJyaXZhbF9kYXRlX3llYXIsCiAgICAgICAgIC1yZXNlcnZhdGlvbl9zdGF0dXMsCiAgICAgICAgIC1yZXNlcnZhdGlvbl9zdGF0dXNfZGF0ZSkgJT4lIAogIGFkZF9uX21pc3MoKSAlPiUgCiAgZmlsdGVyKG5fbWlzc19hbGwgPT0gMCkgJT4lIAogIHNlbGVjdCgtbl9taXNzX2FsbCkKCnNldC5zZWVkKDQ5NCkKYGBgCgpTcGxpdCBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3RpbmcgKDUwLzUwKQpgYGB7cn0KI3JhbmRvbWx5IGFzc2lnbiA1MCUgb2YgdGhlIGRhdGEgdG8gdHJhaW5pbmcKaG90ZWxzX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoaG90ZWxzX21vZCwgcHJvcCA9IC41KQoKaG90ZWxzX3NwbGl0CgojdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YXNldHMKaG90ZWxzX3RyYWluaW5nIDwtIHRyYWluaW5nKGhvdGVsc19zcGxpdCkKaG90ZWxzX3Rlc3RpbmcgPC0gdGVzdGluZyhob3RlbHNfc3BsaXQpCmBgYAoKCjQuIEluIHRoaXMgbmV4dCBzdGVwLCB3ZSBhcmUgZ29pbmcgdG8gZG8gdGhlIHByZS1wcm9jZXNzaW5nLiBVc3VhbGx5LCBJIHdvbid0IHRlbGwgeW91IGV4YWN0bHkgd2hhdCB0byBkbyBoZXJlLCBidXQgZm9yIHlvdXIgZmlyc3QgZXhlcmNpc2UsIEknbGwgdGVsbCB5b3UgdGhlIHN0ZXBzLiAKCiogU2V0IHVwIHRoZSByZWNpcGUgd2l0aCBgaXNfY2FuY2VsZWRgIGFzIHRoZSBvdXRjb21lIGFuZCBhbGwgb3RoZXIgdmFyaWFibGVzIGFzIHByZWRpY3RvcnMgKEhJTlQ6IGB+LmApLiAgCiogVXNlIGEgYHN0ZXBfWFhYKClgIGZ1bmN0aW9uIG9yIGZ1bmN0aW9ucyAoSSB0aGluayB0aGVyZSBhcmUgb3RoZXIgd2F5cyB0byBkbyB0aGlzLCBidXQgSSBmb3VuZCBgc3RlcF9tdXRhdGVfYXQoKWAgZWFzaWVzdCkgdG8gY3JlYXRlIHNvbWUgaW5kaWNhdG9yIHZhcmlhYmxlcyBmb3IgdGhlIGZvbGBsb3dpbmcgdmFyaWFibGVzOiBgY2hpbGRyZW5gLCBgYmFiaWVzYCwgYW5kIGBwcmV2aW91c19jYW5jZWxsYXRpb25zYC4gU28sIHRoZSBuZXcgdmFyaWFibGUgc2hvdWxkIGJlIGEgMSBpZiB0aGUgb3JpZ2luYWwgaXMgbW9yZSB0aGFuIDAgYW5kIDAgb3RoZXJ3aXNlLiBNYWtlIHN1cmUgeW91IGRvIHRoaXMgaW4gYSB3YXkgdGhhdCBhY2NvdW50cyBmb3IgdmFsdWVzIHRoYXQgbWF5IGJlIGxhcmdlciB0aGFuIGFueSB3ZSBzZWUgaW4gdGhlIGRhdGFzZXQuICAKKiBGb3IgdGhlIGBhZ2VudGAgYW5kIGBjb21wYW55YCB2YXJpYWJsZXMsIG1ha2UgbmV3IGluZGljYXRvciB2YXJpYWJsZXMgdGhhdCBhcmUgMSBpZiB0aGV5IGhhdmUgYSB2YWx1ZSBvZiBgTlVMTGAgYW5kIDAgb3RoZXJ3aXNlLiAKKiBVc2UgYGZjdF9sdW1wX24oKWAgdG8gbHVtcCB0b2dldGhlciBjb3VudHJpZXMgdGhhdCBhcmVuJ3QgaW4gdGhlIHRvcCA1IG1vc3Qgb2NjdXJyaW5nLiAKKiBJZiB5b3UgdXNlZCBuZXcgbmFtZXMgZm9yIHNvbWUgb2YgdGhlIG5ldyB2YXJpYWJsZXMgeW91IGNyZWF0ZWQsIHRoZW4gcmVtb3ZlIGFueSB2YXJpYWJsZXMgdGhhdCBhcmUgbm8gbG9uZ2VyIG5lZWRlZC4gCiogVXNlIGBzdGVwX25vcm1hbGl6ZSgpYCB0byBjZW50ZXIgYW5kIHNjYWxlIGFsbCB0aGUgbm9uLWNhdGVnb3JpY2FsIHByZWRpY3RvciB2YXJpYWJsZXMuIChEbyB0aGlzIEJFRk9SRSBjcmVhdGluZyBkdW1teSB2YXJpYWJsZXMuIFdoZW4gSSB0cmllZCB0byBkbyBpdCBhZnRlciwgSSByYW4gaW50byBhbiBlcnJvciAtIEknbSBzdGlsbCBpbnZlc3RpZ2F0aW5nIHdoeS4pCiogQ3JlYXRlIGR1bW15IHZhcmlhYmxlcyBmb3IgYWxsIGZhY3RvcnMvY2F0ZWdvcmljYWwgcHJlZGljdG9yIHZhcmlhYmxlcyAobWFrZSBzdXJlIHlvdSBoYXZlIGAtYWxsX291dGNvbWVzKClgIGluIHRoaXMgcGFydCEhKS4gIAoqIFVzZSB0aGUgYHByZXAoKWAgYW5kIGBqdWljZSgpYCBmdW5jdGlvbnMgdG8gYXBwbHkgdGhlIHN0ZXBzIHRvIHRoZSB0cmFpbmluZyBkYXRhIGp1c3QgdG8gY2hlY2sgdGhhdCBldmVyeXRoaW5nIHdlbnQgYXMgcGxhbm5lZC4KYGBge3J9Cgpob3RlbHNfcmVjaXBlIDwtIHJlY2lwZShpc19jYW5jZWxlZCB+IC4sIGRhdGEgPSBob3RlbHNfdHJhaW5pbmcpICU+JQogIHN0ZXBfbXV0YXRlKGNoaWxkcmVuID0gaWZlbHNlKGNoaWxkcmVuID4gMCwxLDApLCBiYWJpZXMgPSBpZmVsc2UoYmFiaWVzID4gMCwxLDApLCBwcmV2aW91c19jYW5jZWxsYXRpb25zID0gaWZlbHNlKHByZXZpb3VzX2NhbmNlbGxhdGlvbnMgPiAwLDEsMCkpICU+JQogIHN0ZXBfbXV0YXRlKGFnZW50X251bGwgPSBpZmVsc2UoYWdlbnQgPT0gJ05VTEwnLCAxLCAwKSwgY29tcGFueV9udWxsID0gaWZlbHNlKGNvbXBhbnk9PSdOVUxMJywxLDApKSAlPiUKICBzdGVwX211dGF0ZShjb3VudHJ5ID0gZmN0X2x1bXBfbihjb3VudHJ5LG49NSkpICU+JQogIHN0ZXBfcm0oYWdlbnQsIGNvbXBhbnkpICU+JQogIHN0ZXBfbm9ybWFsaXplKGFsbF9wcmVkaWN0b3JzKCksLWFsbF9ub21pbmFsKCkpICU+JSAjbnVtZXJpYyB2YXJpYWJsZXMgd2lsbCBub3cgaGF2ZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgMSBhbmQgbWVhbiBvZiB6ZXJvCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLC1hbGxfb3V0Y29tZXMoKSkKCgojdXNpbmcgcHJlcCBhbmQganVpY2UgdG8gbWFrZSBzdXJlIGV2ZXJ5dGhpbmcgd2VudCBhcyBwbGFubmVkCmhvdGVsc19yZWNpcGUgJT4lCiAgcHJlcChob3RlbHNfdHJhaW5pbmcpICU+JQogIGp1aWNlKCkKYGBgCgoKNS4gSW4gdGhpcyBzdGVwIHdlIHdpbGwgc2V0IHVwIGEgTEFTU08gbW9kZWwgYW5kIHdvcmtmbG93LgoKKiBJbiBnZW5lcmFsLCB3aHkgd291bGQgd2Ugd2FudCB0byB1c2UgTEFTU08gaW5zdGVhZCBvZiByZWd1bGFyIGxvZ2lzdGljIHJlZ3Jlc3Npb24/IChISU5UOiB0aGluayBhYm91dCB3aGF0IGhhcHBlbnMgdG8gdGhlIGNvZWZmaWNpZW50cykuCgoqKldlIHdvdWxkIHdhbnQgdG8gdXNlIExBU1NPIGluc3RlYWQgb2YgcmVndWxhciBsb2dpc3RpYyByZWdyZXNzaW9uIHRvIGJldHRlciBhdm9pZCBvdmVyZml0dGluZyBvdXIgbW9kZWwgdG8gdGhlIHRyYWluaW5nIGRhdGFzZXQgKHRoZXJlYnkgaGF2aW5nIGl0IGRvIHBvb3JseSB3aXRoIHRoZSB0ZXN0aW5nIGFuZCBvdGhlciBkYXRhc2V0cykuICBMQVNTTyBhc3NpZ25zIGEgcGVuYWx0eSB0byBsZXNzIGltcG9ydGFudCBjb2VmZmljaWVudHMgdG8gc2hyaW5rIHRoZW0gdG8gemVyby4gIFRoZSBwZW5hbHR5ICgkXGxhbWJkYSQgY2FuIGJlIGZyb20gMCB0byBpbmZpbml0eSkgdGhhdCBtaW5pbWl6ZXMgdGhlIHN1bSBvZiB0aGUgc3F1YXJlZCByZXNpZHVhbHMgaXMgdGhlIG9uZSB0aGF0IExBU1NPIGNob29zZXMuKioKCiogRGVmaW5lIHRoZSBtb2RlbCB0eXBlLCBzZXQgdGhlIGVuZ2luZSwgc2V0IHRoZSBgcGVuYWx0eWAgYXJndW1lbnQgdG8gYHR1bmUoKWAgYXMgYSBwbGFjZWhvbGRlciwgYW5kIHNldCB0aGUgbW9kZS4gIApgYGB7cn0KaG90ZWxzX2xhc3NvX21vZCA8LQogICMgRGVmaW5lIGEgTEFTU08gbW9kZWwKICBsb2dpc3RpY19yZWcobWl4dHVyZT0xKSAlPiUKICAjIFNldCB0aGUgZW5naW5lCiAgc2V0X2VuZ2luZSgiZ2xtbmV0IikgJT4lCiAgI3BhcmFtZXRlcnMgd2Ugd2lsbCB0dW5lCiAgc2V0X2FyZ3MocGVuYWx0eSA9IHR1bmUoKSkgJT4lCiAgIyBTZXQgbW9kZQogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCgpob3RlbHNfbGFzc29fbW9kCmBgYAoKKiBDcmVhdGUgYSB3b3JrZmxvdyB3aXRoIHRoZSByZWNpcGUgYW5kIG1vZGVsLiAoY29tYmluZXMgdGhlIHByZXByb2Nlc3NpbmcgYW5kIG1vZGVsIGRlZmluaXRpb24gc3RlcHMpCmBgYHtyfQpob3RlbHNfbGFzc29fd2YgPC0KICAjc2V0IHVwIHRoZSB3b3JrZmxvdwogIHdvcmtmbG93KCkgJT4lCiAgI2FkZCB0aGUgcmVjaXBlCiAgYWRkX3JlY2lwZShob3RlbHNfcmVjaXBlKSAlPiUKICAjYWRkIHRoZSBtb2RlbGluZwogIGFkZF9tb2RlbChob3RlbHNfbGFzc29fbW9kKQoKaG90ZWxzX2xhc3NvX3dmCmBgYAoKNi4gSW4gdGhpcyBzdGVwLCB3ZSdsbCB0dW5lIHRoZSBtb2RlbCBhbmQgZml0IHRoZSBtb2RlbCB1c2luZyB0aGUgYmVzdCB0dW5pbmcgcGFyYW1ldGVyIHRvIHRoZSBlbnRpcmUgdHJhaW5pbmcgZGF0YXNldC4KCiogQ3JlYXRlIGEgNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24gc2FtcGxlLiBXZSdsbCB1c2UgdGhpcyBsYXRlci4gSSBoYXZlIHNldCB0aGUgc2VlZCBmb3IgeW91LiAgCiogVXNlIHRoZSBgZ3JpZF9yZWd1bGFyKClgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIGdyaWQgb2YgMTAgcG90ZW50aWFsIHBlbmFsdHkgcGFyYW1ldGVycyAod2UncmUga2VlcGluZyB0aGlzIHNvcnQgb2Ygc21hbGwgYmVjYXVzZSB0aGUgZGF0YXNldCBpcyBwcmV0dHkgbGFyZ2UpLiBVc2UgdGhhdCB3aXRoIHRoZSA1LWZvbGQgY3YgZGF0YSB0byB0dW5lIHRoZSBtb2RlbC4gIAoqIFVzZSB0aGUgYHR1bmVfZ3JpZCgpYCBmdW5jdGlvbiB0byBmaXQgdGhlIG1vZGVscyB3aXRoIGRpZmZlcmVudCB0dW5pbmcgcGFyYW1ldGVycyB0byB0aGUgZGlmZmVyZW50IGNyb3NzLXZhbGlkYXRpb24gc2V0cy4gIAoqIFVzZSB0aGUgYGNvbGxlY3RfbWV0cmljcygpYCBmdW5jdGlvbiB0byBjb2xsZWN0IGFsbCB0aGUgbWV0cmljcyBmcm9tIHRoZSBwcmV2aW91cyBzdGVwIGFuZCBjcmVhdGUgYSBwbG90IHdpdGggdGhlIGFjY3VyYWN5IG9uIHRoZSB5LWF4aXMgYW5kIHRoZSBwZW5hbHR5IHRlcm0gb24gdGhlIHgtYXhpcy4gUHV0IHRoZSB4LWF4aXMgb24gdGhlIGxvZyBzY2FsZS4gIAoqIFVzZSB0aGUgYHNlbGVjdF9iZXN0KClgIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlciwgZml0IHRoZSBtb2RlbCB1c2luZyB0aGF0IHR1bmluZyBwYXJhbWV0ZXIgdG8gdGhlIGVudGlyZSB0cmFpbmluZyBzZXQgKEhJTlQ6IGBmaW5hbGl6ZV93b3JrZmxvdygpYCBhbmQgYGZpdCgpYCksIGFuZCBkaXNwbGF5IHRoZSBtb2RlbCByZXN1bHRzIHVzaW5nIGBwdWxsX3dvcmtmbG93X2ZpdCgpYCBhbmQgYHRpZHkoKWAuIEFyZSB0aGVyZSBzb21lIHZhcmlhYmxlcyB3aXRoIGNvZWZmaWNpZW50cyBvZiAwPwoKYGBge3J9CnNldC5zZWVkKDQ5NCkgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CgojNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24KaG90ZWxzX2N2IDwtIHZmb2xkX2N2KGhvdGVsc190cmFpbmluZywgdiA9IDUpCgojY2hvb3NlIHBlbmFsdHkgcGFyYW1ldGVyIHZhbHVlcwpwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKHBlbmFsdHkoKSwgbGV2ZWxzID0gMTApCnBlbmFsdHlfZ3JpZAoKI2ZpdCB0aGUgbW9kZWxzIHdpdGggZGlmZmVyZW50IHR1bmluZyBwYXJhbWV0ZXJzIHRvIHRoZSBkaWZmZXJlbnQgY3Jvc3MtdmFsaWRhdGlvbiBzZXRzCmhvdGVsc19sYXNzb190dW5lIDwtCiAgaG90ZWxzX2xhc3NvX3dmICU+JQogIHR1bmVfZ3JpZCgKICAgIHJlc2FtcGxlcyA9IGhvdGVsc19jdiwKICAgIGdyaWQgPSBwZW5hbHR5X2dyaWQKICApCgpob3RlbHNfbGFzc29fdHVuZQoKaG90ZWxzX2FjY3VyYWN5IDwtIGhvdGVsc19sYXNzb190dW5lICU+JQogIGNvbGxlY3RfbWV0cmljcygpICU+JQogIGZpbHRlcigubWV0cmljID09ICJhY2N1cmFjeSIpCgpob3RlbHNfYWNjdXJhY3kKCmdncGxvdChob3RlbHNfYWNjdXJhY3ksIGFlcyh4ID0gcGVuYWx0eSwgeSA9IG1lYW4pKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9sb2cxMCgKICAgYnJlYWtzID0gc2NhbGVzOjp0cmFuc19icmVha3MoImxvZzEwIiwgZnVuY3Rpb24oeCkgMTBeeCksCiAgIGxhYmVscyA9IHNjYWxlczo6dHJhbnNfZm9ybWF0KCJsb2cxMCIsc2NhbGVzOjptYXRoX2Zvcm1hdCgxMF4ueCkpKSArCiAgZ2d0aXRsZSgiQWNjdXJhY3kgb3ZlciBkaWZmZXJlbnQgcGVuYWx0aWVzIikgKwogIHlsYWIoImFjY3VyYWN5IikKCiNzZWxlY3QgYmVzdCB0dW5pbmcgcGFyYW1ldGVyIChhY2N1cmFjeS13aXNlKQpiZXN0X3BhcmFtX2hvdGVscyA8LSBob3RlbHNfbGFzc29fdHVuZSAlPiUKICBzZWxlY3RfYmVzdChtZXRyaWMgPSAiYWNjdXJhY3kiKQoKYmVzdF9wYXJhbV9ob3RlbHMKYGBgCgpGaXR0aW5nIHRoZSBtb2RlbCB3aXRoIHRoZSBiZXN0IHR1bmluZyBwYXJhbWV0ZXIgYWNjb3JkaW5nIHRvIHNlbGVjdF9iZXN0CmBgYHtyfQojY3JlYXRlIHdvcmtmbG93CmhvdGVsc19sYXNzb19maW5hbF93ZiA8LSBob3RlbHNfbGFzc29fd2YgJT4lCiAgZmluYWxpemVfd29ya2Zsb3coYmVzdF9wYXJhbV9ob3RlbHMpCgpob3RlbHNfbGFzc29fZmluYWxfd2YKCiNmaXQgdG8gdGhlIHRyYWluaW5nIGRhdGEKaG90ZWxzX2xhc3NvX2ZpbmFsX21vZCA8LSBob3RlbHNfbGFzc29fZmluYWxfd2YgJT4lCiAgZml0KGRhdGEgPSBob3RlbHNfdHJhaW5pbmcpCgpob3RlbHNfbGFzc29fZmluYWxfbW9kICU+JSAKICBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JSAKICB0aWR5KCkgCmBgYAoKKipZZXMsIHNvbWUgdmFyaWFibGVzIG5vdyBoYXZlIGNvZWZmaWNpZW50cyBvZiB6ZXJvOiBhcnJpdmFsX2RhdGVfbW9udGhfT2N0b2JlciwgbWVhbF9GQiwgbWFya2V0X3NlZ21lbnRfR3JvdXBzLCBtYXJrZXRfc2VnbWVudF9VbmRlZmluZWQsIGRpc3RyaWJ1dGlvbl9jaGFubmVsX1VuZGVmaW5lZCwgYXNzaWduZWRfcm9vbV90eXBlX0wsIGFuZCBhc3NpZ25lZF9yb29tX3R5cGVfUC4qKgoKCjcuIE5vdyB0aGF0IHdlIGhhdmUgYSBtb2RlbCwgbGV0J3MgZXZhbHVhdGUgaXQgYSBiaXQgbW9yZS4gQWxsIHdlIGhhdmUgbG9va2VkIGF0IHNvIGZhciBpcyB0aGUgY3Jvc3MtdmFsaWRhdGVkIGFjY3VyYWN5IGZyb20gdGhlIHByZXZpb3VzIHN0ZXAuIAoKKiBDcmVhdGUgYSB2YXJpYWJsZSBpbXBvcnRhbmNlIGdyYXBoLiBXaGljaCB2YXJpYWJsZXMgc2hvdyB1cCBhcyB0aGUgbW9zdCBpbXBvcnRhbnQ/IEFyZSB5b3Ugc3VycHJpc2VkPyAgCmBgYHtyfQojIFZpc3VhbGl6ZSB2YXJpYWJsZSBpbXBvcnRhbmNlCmhvdGVsc19sYXNzb19maW5hbF9tb2QgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIAogIHZpcCgpCmBgYAoKKipGcm9tIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3QsIHdlIHNlZSB0aGF0IHJlc2VydmVkX3Jvb21fdHlwZV9QLCBkZXBvc2l0X3R5cGVfTm9uLlJlZnVuZCwgYW5kIGFzc2lnbmVkX3Jvb21fdHlwZV9JIGFyZSB0aGUgdG9wIHRocmVlIG1vc3QgaW1wb3J0YW50IHByZWRpY3RvcnMgb2YgY2FuY2VsaW5nIGEgcmVzZXJ2YXRpb24uIEkgYW0gbm90IHN1cmUgd2hhdCByb29tIHR5cGVzIFAgYW5kIEkgYXJlLCBidXQgSSBhbSBub3Qgc3VycHJpc2VkIHRoYXQgcHV0dGluZyBhIG5vbi1yZWZ1bmRhYmxlIGRlcG9zaXQgZG93biBpcyBhIHN0cm9uZyBpbmRpY2F0b3IgZm9yIHdoZXRoZXIgb3Igbm90IHRoZSBib29raW5nIGdvdCBjYW5jZWxlZCAoYmVjYXVzZSBwZW9wbGUgZG9uJ3QgbGlrZSB0byBsb3NlIG1vbmV5IHNvIHRoZXkgbWF5IGJlIGxlc3MgbGlrZWx5IHRvIGNhbmNlbCkuICBJZiByb29tIHR5cGUgaW5kaWNhdGVkIHRoZSBzaXplIGFuZCBudW1iZXIgb2YgYmVkcywgSSBjb3VsZCBzZWUgaXQgYmVpbmcgYSByZWxldmFudCBwcmVkaWN0b3IgYmVjYXVzZSBwZW9wbGUgZ29pbmcgb24gYnVzaW5lc3MgdHJpcHMgKHNtYWxsIGJlZCwgc2luZ2xlIHJvb20gbW9zdCBsaWtlbHkpIG1heSBiZSBsZXNzIGxpa2VseSB0byBjYW5jZWwuKioKCiogVXNlIHRoZSBgbGFzdF9maXQoKWAgZnVuY3Rpb24gdG8gZml0IHRoZSBmaW5hbCBtb2RlbCBhbmQgdGhlbiBhcHBseSBpdCB0byB0aGUgdGVzdGluZyBkYXRhLiBSZXBvcnQgdGhlIG1ldHJpY3MgZnJvbSB0aGUgdGVzdGluZyBkYXRhIHVzaW5nIHRoZSBgY29sbGVjdF9tZXRyaWNzKClgIGZ1bmN0aW9uLiBIb3cgZG8gdGhleSBjb21wYXJlIHRvIHRoZSBjcm9zcy12YWxpZGF0ZWQgbWV0cmljcz8KYGBge3J9CiMgRml0IG1vZGVsIHdpdGggYmVzdCB0dW5pbmcgcGFyYW1ldGVyKHMpIHRvIHRyYWluaW5nIGRhdGEgYW5kIGFwcGx5IHRvIHRlc3QgZGF0YQpob3RlbHNfbGFzc29fdGVzdCA8LSBob3RlbHNfbGFzc29fZmluYWxfd2YgJT4lIAogIGxhc3RfZml0KGhvdGVsc19zcGxpdCkKCiMgTWV0cmljcyBmb3IgbW9kZWwgYXBwbGllZCB0byB0ZXN0IGRhdGEKaG90ZWxzX2xhc3NvX3Rlc3QgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpCgojbWV0cmljcyBmb3IgbW9kZWwgYXBwbGllZCB0byBjdiBkYXRhCmhvdGVsc19sYXNzb190dW5lICU+JQogIGNvbGxlY3RfbWV0cmljcygpCgojY29tcGFyaW5nIG1ldHJpY3MKZ2dwbG90KGhvdGVsc19hY2N1cmFjeSwgYWVzKHggPSBwZW5hbHR5LCB5ID0gbWVhbikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2xvZzEwKAogICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgbGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIixzY2FsZXM6Om1hdGhfZm9ybWF0KDEwXi54KSkpICsKICBnZ3RpdGxlKCJBY2N1cmFjeSBvdmVyIGRpZmZlcmVudCBwZW5hbHRpZXMiKSArCiAgeWxhYigiYWNjdXJhY3kiKSArCiAgZ2VvbV9wb2ludChhZXMoeD0wLjAwMDQ2NDE1ODksIHkgPSAwLjgxMjgyNTYsIGNvbCA9ICJyZWQiKSkgKwogIGxhYnMoY29sb3IgPSAiVGVzdCBkYXRhIG1ldHJpYyIpCmBgYAoKKipUaGUgYWNjdXJhY3kgb2YgbW9kZWwgYXBwbGllZCB0byBDViBkYXRhIHdpdGggdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlciBpcyBTTElHSFRMWSBsb3dlciB0aGFuIHRoZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWwgYXBwbGllZCB0byB0aGUgdGVzdCBkYXRhIHdpdGggdGhlIHNhbWUgdHVuaW5nIHBhcmFtZXRlci4gIFdlIGNhbiBzZWUgdGhpcyBpbiB0aGUgcGxvdCwgd2hlcmUgdGhlIHJlZCBkb3QgKHRlc3QgZGF0YSkgaXMgc2xpZ2h0bHkgaGlnaGVyIHRoYW4gdGhlIGJsYWNrIGRvdCAodHJhaW5pbmcgZGF0YSkuKioKCiogVXNlIHRoZSBgY29sbGVjdF9wcmVkaWN0aW9ucygpYCBmdW5jdGlvbiB0byBmaW5kIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBhbmQgY2xhc3NlcyBmb3IgdGhlIHRlc3QgZGF0YS4gU2F2ZSB0aGlzIHRvIGEgbmV3IGRhdGFzZXQgY2FsbGVkIGBwcmVkc2AuIFRoZW4sIHVzZSB0aGUgYGNvbmZfbWF0KClgIGZ1bmN0aW9uIGZyb20gYGRpYWxzYCAocGFydCBvZiBgdGlkeW1vZGVsc2ApIHRvIGNyZWF0ZSBhIGNvbmZ1c2lvbiBtYXRyaXggc2hvd2luZyB0aGUgcHJlZGljdGVkIGNsYXNzZXMgdnMuIHRoZSB0cnVlIGNsYXNzZXMuIFdoYXQgaXMgdGhlIHRydWUgcG9zaXRpdmUgcmF0ZSAoc2Vuc2l0aXZpdHkpPyBXaGF0IGlzIHRoZSB0cnVlIG5lZ2F0aXZlIHJhdGUgKHNwZWNpZmljaXR5KT8gU2VlIHRoaXMgW1dpa2lwZWRpYV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQ29uZnVzaW9uX21hdHJpeCkgcmVmZXJlbmNlIGlmIHlvdSAobGlrZSBtZSkgdGVuZCB0byBmb3JnZXQgdGhlc2UgZGVmaW5pdGlvbnMuCmBgYHtyfQojIHByZWRpY3Rpb25zIGZyb20gdGVzdGluZyBkYXRhCnByZWRzIDwtIGNvbGxlY3RfcHJlZGljdGlvbnMoaG90ZWxzX2xhc3NvX3Rlc3QpCnByZWRzCiNtYWtlIGEgY29uZnVzaW9uIG1hdHJpeApjb25mX21hdChwcmVkcywgLnByZWRfY2xhc3MsIGlzX2NhbmNlbGVkKQpgYGAKCiogVXNlIHRoZSBgcHJlZHNgIGRhdGFzZXQgeW91IGp1c3QgY3JlYXRlZCB0byBjcmVhdGUgYSBkZW5zaXR5IHBsb3Qgb2YgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIG9mIGNhbmNlbGluZyAodGhlIHZhcmlhYmxlIGlzIGNhbGxlZCBgLnByZWRfMWApLCBmaWxsaW5nIGJ5IGBpc19jYW5jZWxlZGAuIFVzZSBhbiBgYWxwaGEgPSAuNWAgYW5kIGBjb2xvciA9IE5BYCBpbiB0aGUgYGdlb21fZGVuc2l0eSgpYC4KYGBge3J9CmdncGxvdChwcmVkcywgYWVzKHggPSAucHJlZF8xLCBmaWxsID0gaXNfY2FuY2VsZWQpKSArCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41LCBjb2xvciA9IE5BKSArCiAgZ2d0aXRsZSgiUHJlZGljdGVkIHByb2JhYmlsaXRpZXMgb2YgY2FuY2VsbGluZyAodGVzdCBkYXRhKSIpCmBgYAoKCkFuc3dlciB0aGVzZSBxdWVzdGlvbnM6CgphLiBXaGF0IHdvdWxkIHRoaXMgZ3JhcGggbG9vayBsaWtlIGZvciBhIG1vZGVsIHdpdGggYW4gYWNjdXJhY3kgdGhhdCB3YXMgY2xvc2UgdG8gMT8KCioqQSBtb2RlbCB3aXRoIGFuIGFjY3VyYWN5IHRoYXQgd2FzIGNsb3NlIHRvIDEgd291bGQgaGF2ZSBkZW5zaXRpZXMgYWxtb3N0IHNvbGVseSBhdCB4LWF4aXMgdmFsdWVzIG9mIDAgYW5kIDEuKioKCmIuIE91ciBwcmVkaWN0aW9ucyBhcmUgY2xhc3NpZmllZCBhcyBjYW5jZWxlZCBpZiB0aGVpciBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgY2FuY2VsaW5nIGlzIGdyZWF0ZXIgdGhhbiAuNS4gSWYgd2Ugd2FudGVkIHRvIGhhdmUgYSBoaWdoIHRydWUgcG9zaXRpdmUgcmF0ZSwgc2hvdWxkIHdlIG1ha2UgdGhlIGN1dG9mZiBmb3IgcHJlZGljdGVkIGFzIGNhbmNlbGVkIGhpZ2hlciBvciBsb3dlciB0aGFuIC41PwoKKipJZiB3ZSB3YW50ZWQgYSBoaWdoIHRydWUgcG9zaXRpdmUgcmF0ZSAoY29ycmVjdGx5IHByZWRpY3RpbmcgY2FuY2VsZWQgcmVzZXJ2YXRpb25zIGFzIGNhbmNlbGVkKSB0aGVuIHdlIHdvdWxkIHdhbnQgdG8gbWFrZSB0aGUgY3V0b2ZmIGZvciAicHJlZGljdGVkIGFzIGNhbmNlbGVkIiBsb3dlciB0aGFuIDAuNSBiZWNhdXNlIHRoYXQgd291bGQgYXNzaWduIG1vcmUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgdG8gY2FuY2VsZWQ9eWVzLioqCgpjLiBXaGF0IGhhcHBlbnMgdG8gdGhlIHRydWUgbmVnYXRpdmUgcmF0ZSBpZiB3ZSB0cnkgdG8gZ2V0IGEgaGlnaGVyIHRydWUgcG9zaXRpdmUgcmF0ZT8KCioqSWYgd2UgdHJ5IHRvIGdldCBhIHRydWUgcG9zaXRpdmUgcmF0ZSwgdGhlIHRydWUgbmVnYXRpdmUgcmF0ZSB3aWxsIHN1YnNlcXVlbnRseSBiZSBsb3dlciBiZWNhdXNlIHdlIGFyZSBjbGFzc2lmeWluZyBtb3JlIHJlc2VydmF0aW9ucyBhcyAiY2FuY2VsZWQiIHdoaWNoIHRoZXJlYnkgY2xhc3NpZmllcyBsZXNzIGFzICJub3QgY2FuY2VsZWQiLioqCgo4LiBMZXQncyBzYXkgdGhhdCB0aGlzIG1vZGVsIGlzIGdvaW5nIHRvIGJlIGFwcGxpZWQgdG8gYm9va2luZ3MgMTQgZGF5cyBpbiBhZHZhbmNlIG9mIHRoZWlyIGFycml2YWwgYXQgZWFjaCBob3RlbCwgYW5kIHNvbWVvbmUgd2hvIHdvcmtzIGZvciB0aGUgaG90ZWwgd2lsbCBtYWtlIGEgcGhvbmUgY2FsbCB0byB0aGUgcGVyc29uIHdobyBtYWRlIHRoZSBib29raW5nLiBEdXJpbmcgdGhpcyBwaG9uZSBjYWxsLCB0aGV5IHdpbGwgdHJ5IHRvIGFzc3VyZSB0aGF0IHRoZSBwZXJzb24gd2lsbCBiZSBrZWVwaW5nIHRoZWlyIHJlc2VydmF0aW9uIG9yIHRoYXQgdGhleSB3aWxsIGJlIGNhbmNlbGluZyBpbiB3aGljaCBjYXNlIHRoZXkgY2FuIGRvIHRoYXQgbm93IGFuZCBzdGlsbCBoYXZlIHRpbWUgdG8gZmlsbCB0aGUgcm9vbS4gSG93IHNob3VsZCB0aGUgaG90ZWwgZ28gYWJvdXQgZGVjaWRpbmcgd2hvIHRvIGNhbGw/IEhvdyBjb3VsZCB0aGV5IG1lYXN1cmUgd2hldGhlciBpdCB3YXMgd29ydGggdGhlIGVmZm9ydCB0byBkbyB0aGUgY2FsbGluZz8gQ2FuIHlvdSB0aGluayBvZiBhbm90aGVyIHdheSB0aGV5IG1pZ2h0IHVzZSB0aGUgbW9kZWw/CgoqKkhvdGVscyBjb3VsZCB1c2UgdGhpcyBtb2RlbCB0byBjYWxsIGFueW9uZSB3aG9tIHRoZSBtb2RlbCBwcmVkaWN0ZWQgd291bGQgY2FuY2VsIHRoZWlyIHJlc2VydmF0aW9uLCB0byBjb25maXJtIHRoYXQgdGhleSBzdGlsbCB3YW50ZWQgdGhlIHJlc2VydmF0aW9uLiAgVGhleSBjb3VsZCBtZWFzdXJlIHdoZXRoZXIgcHIgbm90IGl0IHdhcyB3b3J0aCB0aGUgZWZmb3J0IHRvIGNhbGwgYnkga2VlcGluZyB0cmFjayBvZiB0aGUgInRydWUgbmVnYXRpdmVzIiAocGVvcGxlIHRoZXkgY2FsbGVkIHdobyBhY3R1YWxseSB3ZXJlIHBsYW5uaW5nIHRvIGNhbmNlbCB0aGVpciByZXNlcnZhdGlvbikgYW5kICJmYWxzZSBuZWdhdGl2ZXMiIChwZW9wbGUgdGhleSBjYWxsZWQgdGhhdCB0aGUgbW9kZWwgcHJlZGljdGVkIHdvdWxkIGNhbmNlbCBidXQgd2VyZW4ndCBhY3R1YWxseSBwbGFubmluZyB0byBjYW5jZWwpIGFuZCBzZWVpbmcgaWYgdGhlIG1vZGVsIGd1ZXNzZWQgcmlnaHQgbW9yZSBvZnRlbiB0aGFuIGl0IGd1ZXNzZWQgaW5jb3JyZWN0bHkgKHRvIGEgc2lnbmlmaWNhbnQgZGVncmVlKS4gIEFub3RoZXIgd2F5IHRoYXQgdGhpcyBtb2RlbCBjb3VsZCBiZSB1c2VkIGlzIHRvIHNlZSB3aG8gaXQgcHJlZGljdHMgaXMgTk9UIGxpa2VseSB0byBjYW5jZWwsIGFuZCB0aGVuIHNlbmRpbmcgdGhlbSBwcm9tb3Rpb25hbCBtYXRlcmlhbCBvciBjYXRlcmluZyB0byB0aG9zZSBwZW9wbGUgaW4gc29tZSB3YXkgdG8gYXR0ZW1wdCB0byBtYWtlIHRoZW0gd2FudCB0byBjb250aW51ZSBib29raW5nIHdpdGggdGhhdCBob3RlbCBjb21wYW55IGFnYWluLioqCgo5LiBIb3cgbWlnaHQgeW91IGdvIGFib3V0IHF1ZXN0aW9uaW5nIGFuZCBldmFsdWF0aW5nIHRoZSBtb2RlbCBpbiB0ZXJtcyBvZiBmYWlybmVzcz8gQXJlIHRoZXJlIGFueSBxdWVzdGlvbnMgeW91IHdvdWxkIGxpa2UgdG8gYXNrIG9mIHRoZSBwZW9wbGUgd2hvIGNvbGxlY3RlZCB0aGUgZGF0YT8gCgoqKkRlcGVuZGluZyBvbiBob3cgdGhlIG1vZGVsIHdvdWxkIGJlIHVzZWQgaW4gdGhlIGZ1dHVyZSwgSSBtaWdodCBjaGFsbGVuZ2UgdGhlaXIgcGljayBvZiB0aGUgcHV0dGluZyBhIG5vbi1yZWZ1bmRhYmxlIGRlcG9zaXQgZG93biB2YXJpYWJsZSB0byBwcmVkaWN0IGNhbmNlbGxhdGlvbnMsIGJlY2F1c2Ugc29tZSBwZW9wbGUgYXJlIGZpbmFuY2lhbGx5IG9yIGxvZ2lzdGljYWxseSB1bmFibGUgdG8gZG8gc28uICBUaGVyZWZvcmUsIGlmIHRoZSBtb2RlbCBjYXVzZWQgaG90ZWxzIHRvIGdpdmUgdGhlbSBwb29yZXIgdHJlYXRtZW50LCB0aGF0IGlzIHVuanVzdC4gIEkgd291bGQgYWxzbyBsaWtlIHRvIGFzayB0aGUgcGVvcGxlIHdobyBjb2xsZWN0ZWQgdGhlIGRhdGEgd2h5IHRoZXkgY2hvc2UgdG8gdXNlIGRhdGEgZnJvbSBob3RlbHMgaW4gUG9ydHVnYWwsIGFuZCBob3cgbXVjaCB0aGV5IHRoaW5rIHRoZSByZXN1bHRzIHdvdWxkIGNoYW5nZSBpZiB5b3UgY29tcGFyZWQgYWNyb3NzIGRpZmZlcmVudCBjb3VudHJpZXMuKioKCgoKIyMgQmlhcyBhbmQgRmFpcm5lc3MKCkxpc3RlbiB0byBEci4gUmFjaGVsIFRob21hcydzICBbQmlhcyBhbmQgRmFpcm5lc3MgbGVjdHVyZV0oaHR0cHM6Ly9ldGhpY3MuZmFzdC5haS92aWRlb3MvP2xlc3Nvbj0yKS4gV3JpdGUgYSBicmllZiBwYXJhZ3JhcGggcmVmbGVjdGluZyBvbiBpdC4gWW91IG1pZ2h0IGFsc28gYmUgaW50ZXJlc3RlZCBpbiByZWFkaW5nIHRoZSBbUHJvUHVibGljYSBhcnRpY2xlXShodHRwczovL3d3dy5wcm9wdWJsaWNhLm9yZy9hcnRpY2xlL21hY2hpbmUtYmlhcy1yaXNrLWFzc2Vzc21lbnRzLWluLWNyaW1pbmFsLXNlbnRlbmNpbmcpIERyLiBUaG9tYXMgcmVmZXJlbmNlcyBhYm91dCB1c2luZyBhIHRvb2wgY2FsbGVkIENPTVBBUyB0byBwcmVkaWN0IHJlY2lkaXZpc20uIFNvbWUgcXVlc3Rpb25zL2lkZWFzIHlvdSBtaWdodCBrZWVwIGluIG1pbmQ6CgoqIERpZCB5b3UgaGVhciBhbnl0aGluZyB0aGF0IHN1cnByaXNlZCB5b3U/ICAKKiBXaHkgaXMgaXQgaW1wb3J0YW50IHRoYXQgd2UgcGF5IGF0dGVudGlvbiB0byBiaWFzIGFuZCBmYWlybmVzcyB3aGVuIHN0dWR5aW5nIGRhdGEgc2NpZW5jZT8gIAoqIElzIHRoZXJlIGEgdHlwZSBvZiBiaWFzIERyLiBUaG9tYXMgZGlzY3Vzc2VkIHRoYXQgd2FzIG5ldyB0byB5b3U/IENhbiB5b3UgdGhpbmsgYWJvdXQgcGxhY2VzIHlvdSBoYXZlIHNlZW4gdGhlc2UgdHlwZXMgb2YgYmlhc2VzPwoKKipBbGdvcml0aG1pYyBiaWFzIGlzIGNydWNpYWwgdG8gYWNrbm93bGVkZ2UgYmVjYXVzZSBhIGJpYXNlZCBhbGdvcml0aG0gY2FuIHVuanVzdGx5IGFuZCBwZXJtYW5lbnRseSBoYXJtIHBlb3BsZSBwZW9wbGVzJyBsaXZlcy4gIFNvbWV0aGluZyBJIHRvb2sgYXdheSBmcm9tIHRoaXMgdGFsayB3YXMgdGhlIGltcG9ydGFuY2Ugb2YgZXhhbWluaW5nIGFjY3VyYWN5IG9uIHN1Ymdyb3VwcyBvZiB0aGUgZGF0YSwgcmF0aGVyIHRoYW4ganVzdCB0aGUgYWNjdXJhY3kgb2YgdGhlIGFsZ29yaXRobSBhcyBhIHdob2xlLiAgSXQgaXMgcXVpdGUgYW5nZXJpbmcgdGhhdCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgdGhhdCB3ZXJlIHByb3ZlbiB0byBiZSBoYXJtZnVsbHkgYmlhc2VkIGFyZSBzdGlsbCBhbGxvd2VkIHRvIGJlIHVzZWQuICBGb3IgZXhhbXBsZSwgZXZlbiB0aG91Z2ggdGhlIGFsZ29yaXRobSB0byBwcmVkaWN0IHJlY2lkaXZpc20gaGFkIGEgaGlnaCBmYWxzZSBwb3NpdGl2ZSByYXRlIGZvciBBZnJpY2FuIEFtZXJpY2FuIHBlb3BsZSBhbmQgYSBoaWdoIGZhbHNlIG5lZ2F0aXZlIHJhdGUgZm9yIHdoaXRlIHBlb3BsZSwgV2lzY29uc2luIHN0aWxsIGNvbnRpbnVlZCB0byB1c2UgaXQuICBJIGRvbid0IHVuZGVyc3RhbmQgaWYgaXQgaXMganVzdCBvdXQgb2YgYSBkZXNpcmUgZm9yIGNvc3QtZWZmZWN0aXZlbmVzcy9lYXNlL2VmZmljaWVuY3ksIGxhY2sgb2Yga25vd2xlZGdlIGFib3V0IHRoZSBwb29yIG1vZGVsIHBlcmZvcm1hbmNlLCBvciBzb21ldGhpbmcgZWxzZS4gIEl0IGp1c3QgZmVlbHMgc28gY3J1ZWwsIHJhY2lzdCAoZXZlbiB0aG91Z2ggcmFjZSB3YXNuJ3QgYW4gaW5wdXQgdmFyaWFibGUgaW4gdGhhdCBtb2RlbCksIGFuZCB1bmV0aGljYWwsIGFuZCBJIGFtIHNob2NrZWQgdGhhdCB0aGV5IHdlcmUgYWxsb3dlZCB0byBjb250aW51ZSB1c2luZyBpdCBvciB0aGF0IGl0IGV2ZW4gZ290IHB1dCBpbnRvIHVzZSBpbiB0aGUgZmlyc3QgcGxhY2UuKioKCioqQW5vdGhlciB0YWtlYXdheSBmcm9tIHRoZSBsZWN0dXJlIGlzIHRoZSBuZWVkIHRvIGdhdGhlciBtb3JlIHJlcHJlc2VudGF0aXZlIGRhdGFzZXRzIHRvIHRyYWluIHlvdXIgYWxnb3JpdGhtIG9uLCBidXQgYWxzbyByZWFsaXppbmcgdGhhdCBtYW55IG9mIHRoZXNlIGNsYXNzaWZpY2F0aW9uIHZhcmlhYmxlcyBhcmUgc29jaWFsIGNvbnN0cnVjdHMgdG8gYmVnaW4gd2l0aCAocmFjZSwgZ2VuZGVyLi4uKSBhbmQgaXMgaW1wb3NzaWJsZSB0byBiZSAxMDAlIHN1cmUgYWJvdXQgZnJvbSBqdXN0IGxvb2tpbmcgYXQgYSBwaWN0dXJlIG9mIHNvbWVvbmUuKioKCioqT3ZlcmFsbCwgaXQncyByZWFsbHkgdW5mb3J0dW5hdGUgdGhhdCBwZW9wbGUgYXJlIG1vcmUgbGlrZWx5IHRvIGFzc3VtZSBhbGdvcml0aG1zIGFyZSBsZXNzIGJpYXNlZCAoYXQgbGVhc3QsIGFjY29yZGluZyB0byB0aGUgdGFsaykgdGhhbiBwZW9wbGUgbWFraW5nIHRoZSBkZWNpc2lvbnMgYmVjYXVzZSBwZW9wbGUgYXJlIGNyZWF0aW5nIHRob3NlIGFsZ29yaXRobXMgaW4gdGhlIGZpcnN0IHBsYWNlLiBJIHdvdWxkIHBvc2UgdGhhdCBwZW9wbGUgaGF2ZSB0aGUgYWJpbGl0eSB0byBiZSBtb3JlIG51YW5jZWQgdGhhbiBhbGdvcml0aG1zIHdoZW4gdGhleSBtYWtlIGRlY2lzaW9ucyBiZWNhdXNlIGlkZWFsbHkgdGhleSBjYW4gdGFrZSBhIHN0ZXAgYmFjayBhbmQgYWNrbm93bGVkZ2UgdGhlaXIgYmlhc2VzIGJlZm9yZSBtYWtpbmcgc25hcCBqdWRnZW1lbnRzIChvciBwYXVzaW5nIHRvIGNoYWxsZW5nZSB0aGVpciBzbmFwIGp1ZGdlbWVudHMpIGFib3V0IHBlb3BsZSB0aGF0IGNhbiBtYWtlIG9yIGJyZWFrIHRoZWlyIGZ1dHVyZXMuKioK